﻿using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace WolfGame {

  /// <summary>
  /// This class takes care of all rendering
  /// </summary>
  class v_Renderer : Form {

    #region Constants

    private const string SHADER_PATH = "{0}\\Resources\\shader.fx";

    #endregion

    #region Member Variables

    private Common common;
    private System.ComponentModel.Container components; // window container
    private Effect effect;                              //shader
    private Microsoft.DirectX.Direct3D.Font font;       //display text on screen
    private Sprite text;                                //sprite object for displaying FPS

    private int lastTickCount = 1;                  //last count of system time
    private int frames = 0;                         //number of frames drawn since last time
    private float lastFrameRate = 0;                 //the last framerate over a period of time

    #endregion

    #region Constructor

    /// <summary>
    /// Instantiate the class
    /// </summary>
    public v_Renderer(Common common) {
      Console.WriteLine("Initializing renderer...");
      this.common = common;
      components = null;
      this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
      InitializeWindow();
      InitializeRenderingDevice();
    }

    #endregion

    #region Initialization

    /// <summary>
    /// Load the window settings
    /// </summary>
    private void InitializeWindow() {
      if (!common.Configuration.Fullscreen)
        Cursor.Hide();
      this.components = new System.ComponentModel.Container();
      this.Size = new System.Drawing.Size(common.Configuration.ScreenWidth,
        common.Configuration.ScreenHeight);
    }

    /// <summary>
    /// Initialize the hardware device and load shaders
    /// </summary>
    private void InitializeRenderingDevice() {
      // Create and set the device rendering settings
      PresentParameters pp = new PresentParameters();
      pp.Windowed = !common.Configuration.Fullscreen;
      pp.SwapEffect = SwapEffect.Discard;
      pp.AutoDepthStencilFormat = DepthFormat.D16;
      pp.EnableAutoDepthStencil = true;

      // Set the backbuffer
      if (common.Configuration.Fullscreen) {
        pp.BackBufferCount = 1;
        pp.BackBufferFormat = Format.X8R8G8B8;
      } else
        pp.BackBufferFormat = Format.R5G6B5;

      // Set the resolution
      pp.BackBufferWidth = common.Configuration.ScreenWidth;
      pp.BackBufferHeight = common.Configuration.ScreenHeight;

      // Get the computer video hardware
      Caps hardware = Manager.GetDeviceCaps(0, DeviceType.Hardware);

      CreateFlags flags = CreateFlags.SoftwareVertexProcessing;

      // Pixelshader 2.0 is required
      // If not available create a Reference device ( must have SDK installed )
      Console.WriteLine("Loading shaders...");
      if (hardware.PixelShaderVersion >= new Version(2, 0) && hardware.VertexShaderVersion >= new Version(1, 1))
        rDevice = new Device(0, DeviceType.Hardware, this, flags, pp);
      else
        rDevice = new Device(0, DeviceType.Reference, this, flags, pp);

      if (hardware.DeviceCaps.SupportsHardwareTransformAndLight)
        flags = CreateFlags.HardwareVertexProcessing;

      if (hardware.DeviceCaps.SupportsPureDevice)
        flags |= CreateFlags.PureDevice;

      String s = null;
      string shaderpath = String.Format(SHADER_PATH,
        System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
      effect = Effect.FromFile(rDevice, shaderpath, null, null, ShaderFlags.None, null, out s);
      if (s != null) {
        // There are Compilation errors show them and then close app
        Cursor.Show();
        rDevice.Dispose();
        this.Visible = false;
        MessageBox.Show(s);
        return;
      }

      rDevice.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
      rDevice.SamplerState[0].MagFilter = TextureFilter.Anisotropic;

      rDevice.RenderState.CullMode = Cull.Clockwise;
      //rDevice.RenderState.CullMode = Cull.None;
      rDevice.RenderState.FillMode = FillMode.Solid;
      rDevice.RenderState.Lighting = false;

      //initialize text object
      text = new Sprite(rDevice);
      font = new Microsoft.DirectX.Direct3D.Font(rDevice, new System.Drawing.Font("Arial", 14));

      //Render States
      rDevice.RenderState.AlphaBlendEnable = true;
      rDevice.RenderState.AlphaFunction = Compare.Greater;
      rDevice.RenderState.AlphaTestEnable = true;
      rDevice.RenderState.DestinationBlend = Blend.InvSourceAlpha;
      rDevice.RenderState.SourceBlend = Blend.SourceAlpha;
      rDevice.RenderState.DiffuseMaterialSource = ColorSource.Material;

      //Color blending ops
      rDevice.TextureState[0].ColorOperation = TextureOperation.Modulate;
      rDevice.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
      rDevice.TextureState[0].ColorArgument2 = TextureArgument.Diffuse;

      //set the first alpha stage to texture alpha
      rDevice.TextureState[0].AlphaOperation = TextureOperation.SelectArg1;
      rDevice.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;
    }

    #endregion

    #region Form Events

    /// <summary>
    /// Close the window and dispose of resources
    /// </summary>
    /// <param name="disposing"></param>
    public void CloseWindow(bool disposing) {
      components.Dispose();
      base.Dispose(disposing);
    }

    /// <summary>
    /// Render the scene
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) {
      rDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.LightBlue, 1.0f, 0);
      rDevice.BeginScene();

      common.Camera.changeCamera();

      rDevice.Transform.Projection = common.Camera.MatProjection;
      rDevice.Transform.View = common.Camera.MatView;

      rDevice.VertexFormat = CustomVertex.PositionNormalTextured.Format;
      rDevice.VertexDeclaration = common.Map.VertexDec;

      rDevice.SetStreamSource(0, common.Map.VertexBuff, 0);
      effect.Technique = "Walls";
      effect.SetValue("xViewProjection", common.Camera.MatView * common.Camera.MatProjection);

      int numPasses = effect.Begin(0);
      //draw all walls, floors, ceilings, and sprites
      for (int i = 0; i < numPasses; i++) {
        for(int j = 0; j < common.Map.Surfaces.Count; j++){          
          effect.BeginPass(i);
          int index = j + 1;
          if (index >= common.Map.Surfaces.Count)
            index = 0;
          effect.SetValue("xTexture", common.Map.Surfaces[index].Texture);
          rDevice.DrawPrimitives(PrimitiveType.TriangleList, common.Map.Surfaces[j].VertexToStart, 2);
          effect.EndPass();
        }
      }
      effect.End();

      //Draw text on the screen
      text.Begin(SpriteFlags.AlphaBlend);
      font.DrawText(text, string.Format("FPS: {0}", ((int)(lastFrameRate)).ToString()),
          new Rectangle(4, 5, 0, 0), DrawTextFormat.NoClip, Color.FromArgb(255, 255, 255, 255));
      font.DrawText(text, "X : " + common.Camera.CameraPosition.X, new Rectangle(4, 24, 0, 0), 
        DrawTextFormat.NoClip, Color.FromArgb(255, 255, 255, 255));
      font.DrawText(text, "Y : " + common.Camera.CameraPosition.Y, new Rectangle(4, 48, 0, 0), 
        DrawTextFormat.NoClip, Color.FromArgb(255, 255, 255, 255));
      font.DrawText(text, "Z : " + common.Camera.CameraPosition.Z, new Rectangle(4, 72, 0, 0), 
        DrawTextFormat.NoClip, Color.FromArgb(255, 255, 255, 255));
      font.DrawText(text, "Rad H : " + common.Camera.HRadians, new Rectangle(4, 96, 0, 0), 
        DrawTextFormat.NoClip, Color.FromArgb(255, 255, 255, 255));
      font.DrawText(text, "Rad V : " + common.Camera.VRadians, new Rectangle(4, 120, 0, 0), 
        DrawTextFormat.NoClip, Color.FromArgb(255, 255, 255, 255));
      text.End();

      rDevice.EndScene();
      rDevice.Present();

      //get the framerate
      frames++;
      if (Math.Abs(Environment.TickCount - lastTickCount) > 1000) {
        lastFrameRate = (float)frames * 1000 / Math.Abs(Environment.TickCount - lastTickCount);
        lastTickCount = Environment.TickCount;
        frames = 0;
      }
      
      common.GameEvents.DoGameEvents();
      this.Invalidate();
      //System.Threading.Thread.Sleep(1000);
    }

    #endregion

    #region Properties

    public Microsoft.DirectX.Direct3D.Device rDevice { get; set; }

    #endregion

  }
}
